<template>
    <div class="box">
        <!-- 左侧物料操作区域 -->
        <div class="material">
            <div class="material-con" v-for="(item, index) in materialData" :key="index">
                <el-tag draggable="true" @dragstart="handleDragStart($event, item)" class="material-con-cont" size="large"
                    :type="item.type">{{ item.text }}</el-tag>
            </div>
        </div>
        <!-- 右侧画布区域 -->
        <div id="container" class="container" @dragover="dragoverDiv" @drop="handleDrag"></div>
        <!-- 弹窗组件 -->
        <div id="popup" style="display: none;" class="popup" ref="popupRef">
            <span @click="delNode()">删除</span>
        </div>
        <HelloWorld></HelloWorld>
    </div>
</template>
  

<script setup lang="ts">
import { onMounted, ref, onUnmounted } from 'vue';
import { Graph, Shape } from '@antv/x6';
import { dataList } from './datam';
import { IItemObj } from './typings';
import { basicPorts, dashLine } from './ports';
import { ElMessage } from 'element-plus';
import cavasData from './cavasData.json';

/**
 * 物料数据
 */
const materialData = ref(dataList);
/**
 * 画布实例对象
 */
let graph:any;

/*
 * 拖动时候携带的业务数据对象
 */
const itemObj = ref<IItemObj>({} as IItemObj);
/**
 * 弹窗dom内容
 */
const popupRef = ref();

/**
 * 选中节点
 */
const activeNode = ref();

onMounted(() => {
    initGraph();
    /**
     * 注册画布节点事件
     */
    nodeDblClick();
    nodeContextMenu();
});

onUnmounted(() => {
    document.removeEventListener('mousemove', funEvent);
    document.removeEventListener('click', funEvent);
})

/**
 * 初始化画布数据
 */
const initGraph = () => {
    let container: any = document.getElementById('container');
    graph = new Graph({
        /**
         * 画布容器
         */
        container: container,
        grid: {
            /**
             * 网格大小 10px
             */
            size: 20,
            /**
             * 渲染网格背景
             */
            visible: true,
        },
        /**
         * 画布支持拖拽
         */
        panning: true,
        /**
         * 对齐线
         */
        snapline: {
            enabled: true,
            tolerance: 10,
        },

        connecting: {
            /**是否自动吸附 */
            snap: true,
            /**是否允许连接到画布空白位置的点 */
            allowBlank: false,
            /**是否允许在相同的起始节点和终止之间创建多条边 */
            allowMulti: false,
            /**是否允许创建循环连线 */
            allowLoop: false,
            /**是否允许边链接到节点（非节点上的链接桩） */
            allowNode: false,
            /**
            * 连接过程中创建新的边
            * @returns {Shape.Edge}
            */
            createEdge() {
                return new Shape.Edge({
                    attrs: {
                        line: dashLine,
                    },
                    /** 
                     * 设置边路径的路由算法 
                     */
                    router: {
                        /**
                         * manhattan：使用曼哈顿路由算法。边的路径由一系列水平线段和垂直线段组成，形成类似于曼哈顿城市街道网格的布局。 
                         * metro：使用类似曼哈顿算法的 Metro 路由算法。允许边以斜线方式连接节点，从而在曼哈顿路由的基础上提供了更多的路径选择
                         * orthogonal：使用直角路由算法。边只能在水平和垂直方向上布局，不允许斜线连接。路径由水平和垂直线段组成，形成直角转弯
                         * oneSide：使用单边路由算法。边连接到节点的一侧，并在水平或垂直方向上布局。这种算法适用于需要将所有边连接到节点的特定一侧的布局场景。
                         */
                        name: 'manhattan',
                        args: {
                            padding: 1,
                        },
                    },
                });
            },
        },
    });
    /**
     * 熏染画布数据
     */
    graph.fromJSON(cavasData);
}

/**
 * 拖动后松开鼠标触发事件
 * @param e 事件对象
 * @param item 业务数据
 */
const handleDragStart = (e, item) => {
    itemObj.value = item;
};

/**
 * 接收拖动过来的元素释放鼠标之后触发， 拖拽元素至画布区域。
 */
const handleDrag = (e) => {
    const { color, text } = itemObj.value;

    if (text === '自定义节点') {
        graph.addNode({
            shape: 'html',
            x: e.layerX - 40,
            y: e.layerY - 20,
            width: 80,
            height: 40,
            html: `
            <div class='htmlNode'>
                <i>🌐</i>
                <p>${text || ''}</p>
              </div>
            `,
            // 创建连接桩
            ports: basicPorts,
        })
    }
    else {
        // 创建节点
        graph.addNode({
            shape: 'rect', // 指定使用何种图形，默认值为 'rect'
            // 减去节点本身宽度的偏移量，减少误差
            x: e.layerX - 40,
            y: e.layerY - 20,
            width: 80,
            height: 40,
            attrs: {
                body: {
                    fill: color, // 背景颜色
                    stroke: '#000',  // 边框颜色
                },
                label: {
                    text: text,    // 文本
                    fill: '#333',    // 文字颜色
                    fontSize: 13,    // 文字大小
                },
            },
            // 创建连接桩
            ports: basicPorts,
        })
    }
};

// 拖动的时候阻止默认事件
const dragoverDiv = (ev) => {
    ev.preventDefault();
}

/**
 * 双击节点显示弹窗
 */
const nodeDblClick = () => {
    graph.on('node:dblclick', ({ e, x, y, node, view }) => {
        // 获取节点文本信息
        const text = node?.store?.data?.attrs?.label?.text;
        ElMessage({
            message: `获取节点文本信息呀：${text}`,
            type: 'success',
        });
    });
};

/**
 * 鼠标右键显示弹窗
 */
const nodeContextMenu = () => {
    // 监听右键菜单事件
    graph.on('node:contextmenu', ({ e, node }) => {
        e.preventDefault(); // 阻止默认的右键菜单事件
        // 获取鼠标位置
        const { clientX, clientY } = e;
        activeNode.value = node;
        popupModel(true, clientX, clientY, e);
    });
    // 点击其他地方隐藏弹窗
    document.addEventListener('click', funEvent);
    // 鼠标移动到页面其他地方隐藏弹窗
    document.addEventListener('mousemove', funEvent);

    /**
     * 鼠标动态添加/删除小工具。
     */
    graph.on('edge:mouseenter', ({ cell }) => {
        cell.addTools([
            { name: 'vertices' },
            {
                name: 'button-remove',
                args: { distance: 20 },
            },
        ])
    })
    graph.on('edge:mouseleave', ({ cell }) => {
        if (cell.hasTool('button-remove')) {
            cell.removeTool('button-remove')
        }
    })
};

/**
 * 执行事件函数
 */
const funEvent = (event) => {
    popupModel(false, 0, 0, event);
}

/**
 * 弹窗的显示隐藏状态
 * @param type true：显示 fasle:隐藏
 * @param clientX x轴距离
 * @param clientY y轴距离
 * @param event 事件对象
 */
const popupModel = (type: boolean, clientX: number, clientY: number, event) => {
    if (type) {
        // 显示弹窗
        // const popup = document.getElementById('popup') as HTMLDivElement;
        const popup = popupRef.value;
        popup.style.display = 'block';
        popup.style.left = `${clientX}px`;
        popup.style.top = `${clientY}px`;
    }
    else {
        const targetElement = event.target as HTMLElement;
        const popup = document.getElementById('popup') as HTMLDivElement;
        // 判断事件的目标元素是否包含在弹窗元素内
        if (!popup.contains(targetElement)) {
            popup.style.display = 'none';
        }
    }
}

/**
 * 删除节点
 */
const delNode = () => {
    if (activeNode.value) {
        // 从图形库中删除节点
        graph.removeNode(activeNode.value);
        // 清空选中的节点
        activeNode.value = null;
        popupRef.value.style.display = 'none';
    }
};

</script>

<style scope lang="less">
@import url("./index.less");
</style>